/*
 *
 * Copyright 2007, 2008 Tiberiumlabs
 *
 * This file is part of Tiberiumlabs Lailaps.
 *
 * Tiberiumlabs Lailaps is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tiberiumlabs Lailaps is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.tiberiumlabs.lailaps.list;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tiberiumlabs.lailaps.Mediator;
import org.tiberiumlabs.lailaps.gui.elements.ExceptionDialog;
import org.tiberiumlabs.lailaps.list.storage.DataHistoryHandler;
import org.tiberiumlabs.lailaps.list.storage.HistoryFilenameFilter;
import org.tiberiumlabs.lailaps.list.storage.HistoryHandler;
import org.tiberiumlabs.lailaps.list.storage.ListAttributes;
import org.tiberiumlabs.lailaps.list.storage.ListHandler;
import static org.tiberiumlabs.lailaps.list.StatusConstants.COMPLETED;
import static org.tiberiumlabs.lailaps.list.StatusConstants.STOPPED;
import org.tiberiumlabs.lailaps.list.storage.ListWriter;
import org.tiberiumlabs.lailaps.settings.ApplicationSettings;
import org.tiberiumlabs.lailaps.settings.Settings;
import org.tiberiumlabs.lailaps.toolkit.StateManager;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 *
 * @author <a href="paranoid.tiberiumlabs@gmail.com">Paranoid</a>
 */
public class ListStorage implements ListAttributes {

    private static final Log LOG = LogFactory.getLog(ListStorage.class);
    private static final String ENCODING = "UTF-8";
    private static final String LS = System.getProperty("line.separator");

    public final void storeList() {
        try {
            LOG.info("[ LIST ] Starting list saving...");

            long start = System.currentTimeMillis();

            String listPath = Settings.getApplicationSettings().getListFile();

            // <editor-fold defaultstate="collapsed" desc=" backup handling ">
            if (Settings.getApplicationSettings().isNeedListBackuping()) {
                File listFileDesc = new File(listPath);
                if (listFileDesc.exists()) {
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
                    // TODO make it "file_yyyy-MM-dd-HH-mm-ss.ext" but not "file.ext_yyyy-MM-dd-HH-mm-ss"
                    while (!listFileDesc.renameTo(new File(listPath + '_' + dateFormat.format(new Date())))) {
                        try {
                            Thread.sleep(500);
                        } catch (Exception ignore) {
                        }
                    }
                }
            }
            // </editor-fold>

            // <editor-fold defaultstate="collapsed" desc=" making dirs to list ">
            File listDir = new File(listPath).getParentFile();
            if (!listDir.exists()) {
                listDir.mkdirs();
            }
            // </editor-fold>

            // <editor-fold defaultstate="collapsed" desc=" saving process ">
            Writer out = new OutputStreamWriter(
                    new GZIPOutputStream(
                    new BufferedOutputStream(
                    new FileOutputStream(listPath))), ENCODING);

            List<DownloadItem> list = Mediator.getMediator().getDownloadsListModel().getDataList();

            new ListWriter().writeList(list, out, ENCODING);

            out.flush();
            out.close();
            // </editor-fold>

            LOG.info("[ LIST ] List with " + list.size() + " saved in " + (System.currentTimeMillis() - start) + " millis.");

        } catch (Exception ex) {
            new ExceptionDialog(ex);
        }
    }

    public final void loadList() {
        try {

            LOG.info("[ LIST ] Loading downloads list...");
            long start = System.currentTimeMillis();

            List<DownloadItem> list;

            File listFile = new File(Settings.getApplicationSettings().getListFile());
            if (listFile.exists()) {

                InputStream input = new GZIPInputStream(new BufferedInputStream(new FileInputStream(listFile)));
                SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
                InputSource inputSource = new InputSource(input);
                inputSource.setEncoding(ENCODING);
                ListHandler handler = new ListHandler();
                parser.parse(inputSource, handler);
                list = handler.getList();
                input.close();

                // <editor-fold defaultstate="collapsed" desc=" calculating stopped and completed quantity ">
                int stopped = 0;
                int completed = 0;

                if (list.size() > 0) {
                    if (list instanceof RandomAccess) {
                        for (int i = 0; i < list.size(); i++) {
                            DownloadItem item = list.get(i);
                            if (item.getStatus() != COMPLETED) {
                                item.setStatus(STOPPED);
                                stopped++;
                            }
                            else {
                                completed++;
                            }
                        }
                    }
                    else {
                        Iterator<DownloadItem> iterator = list.iterator();
                        while (iterator.hasNext()) {
                            DownloadItem item = iterator.next();
                            if (item.getStatus() != COMPLETED) {
                                item.setStatus(STOPPED);
                                stopped++;
                            }
                            else {
                                completed++;
                            }
                        }
                    }
                }

                StateManager stateManager = Mediator.getMediator().getStateManager();
                stateManager.add(StateManager.STOPPED, stopped);
                stateManager.add(StateManager.COMPLETED, completed);
                // </editor-fold>

            }
            else {
                list = new ArrayList<DownloadItem>(32);
            }


            Mediator.getMediator().getDownloadsListModel().setDataList(list);

            LOG.info("[ LIST ] Loaded list with " + list.size() + " items in " + (System.currentTimeMillis() - start) + " millis.");

        } catch (Exception ex) {
            new ExceptionDialog(ex);
        }
    }

    private static boolean savingHistory = false;
    public final void storeHistory() {
        if (savingHistory) {
            return;
        }
        savingHistory = true;
        Thread thread = new Thread("Lailaps history saver") {
            @Override
            public void run() {
                storeHistoryImpl();
            }
        };
        thread.setPriority(Thread.MIN_PRIORITY +1);
        thread.start();
    }
    private final void storeHistoryImpl() {
        try {

            Thread.sleep(666);

            LOG.info("[ HISTORY ] Starting to save history...");
            long startTime = System.currentTimeMillis();

            File historyFolder = new File(ApplicationSettings.HISTORY_FOLDER);
            if (!historyFolder.exists()) {
                historyFolder.mkdirs();
            }

            LinkedList<HistoryItem> historyList = Mediator.getMediator().getDownloadsListModel().getHistoryList();

            if (historyList.isEmpty()) {
                LOG.info("[ HISTORY ] History is empty, nothing to save");
                return;
            }

            // sorting to place by creation date:
            Collections.sort(historyList);

            Date date = new Date(0);
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            HistoryItem item = null;
            Writer writer = null;
            int count = 0;
            while (historyList.size() > 0) {
                synchronized (historyList) {
                    item = historyList.removeFirst();
                }
                count++;
                date.setTime(item.getCreated());
                String s = dateFormat.format(date);
                writer = getHistoryWriterForFile(s);
                writeHistoryItem(item, writer);
            }
            writer.flush();
            writer.close();

            LOG.info("[ HISTORY ] " + count + " history items saved in " + (System.currentTimeMillis() - startTime) + " millis.");

        } catch (Exception ex) {
            new ExceptionDialog(ex);
        } finally {
            savingHistory = false;
        }
    }

    private final void writeHistoryItem(HistoryItem item, Writer out) throws IOException {
        out.write("<record ");

        out.append(URL).append("=\"").append(item.getUrl()).append("\" ");
        out.append(OUTPUT_FILE).append("=\"").append(item.getFilePath()).append("\" ");
        out.append(TIME_CREATED).append("=\"").append(String.valueOf(item.getCreated())).append("\" ");
        long completed = item.getCompleted();
        if (completed != -1) {
            out.append(TIME_COMPLETED).append("=\"").append(String.valueOf(completed)).append("\" ");
        }
        long size = item.getSize();
        if (size > 0) {
            out.append(FULL_SIZE).append("=\"").append(String.valueOf(size)).append("\" ");
        }

        out.write("/>");
        out.write(LS);
    }

    private String historyFilepath;
    private Writer historyFileWriter;
    private final Writer getHistoryWriterForFile(String fileName) throws IOException {
        if (fileName.equals(historyFilepath)) {
            return historyFileWriter;
        }
        if (historyFileWriter != null) {
            historyFileWriter.flush();
            historyFileWriter.close();
        }
        File file = new File(ApplicationSettings.HISTORY_FOLDER + fileName + ".xml.gz");
        boolean needHead = file.createNewFile();
        historyFileWriter = new OutputStreamWriter(new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(file, true))));
        if (needHead) {
            historyFileWriter.write("<?xml version=\"1.0\" encoding=\"");
            historyFileWriter.write(ENCODING);
            historyFileWriter.write("\"?>");
            historyFileWriter.write(LS);
            historyFileWriter.write("<root>");
            historyFileWriter.write(LS);
            historyFileWriter.flush();
        }
        historyFilepath = fileName;
        return historyFileWriter;
    }

    public final void loadHistory() {
        HashMap<String, Integer> historyMap = new HashMap<String, Integer>();
        DataHistoryHandler handler = new DataHistoryHandler(historyMap);
        loadHistoryImpl(handler);
        Mediator.getMediator().getDownloadsListModel().fillHistoryMap(historyMap);
    }

    public final void loadFullHistory(List<HistoryItem> list) {
        HistoryHandler historyHandler = new HistoryHandler(list);
        loadHistoryImpl(historyHandler);
    }

    private final void loadHistoryImpl(DefaultHandler handler) {
        try {
            LOG.info("[ HISTORY ] Loading history...");
            long start = System.currentTimeMillis();

            File historyFolder = new File(ApplicationSettings.HISTORY_FOLDER);
            if (!historyFolder.exists()) {
                LOG.info("[ HISTORY ] History is empty, nothing to load.");
                return;
            }
            File[] files = historyFolder.listFiles(new HistoryFilenameFilter());
            if (files == null || files.length == 0) {
                LOG.info("[ HISTORY ] No history to load.");
                return;
            }
            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                InputStream in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(file)));
                try {
                    InputSource inputSource = new InputSource(in);
                    inputSource.setEncoding(ENCODING);
                    parser.parse(inputSource, handler);
                } catch (SAXException ignore) {
                }
                in.close();
            }

            LOG.info("[ HISTORY ] History loaded in " + (System.currentTimeMillis() - start) + " millis.");

        } catch (Exception ex) {
            new ExceptionDialog(ex);
        }
    }

    public final void savePlainTextList(File file) {
        try {

            long start = System.currentTimeMillis();

            String ls = System.getProperty("line.separator");
            BufferedWriter writer = new BufferedWriter(new FileWriter(file));

            List<DownloadItem> list = Mediator.getMediator().getDownloadsListModel().getDataList();
            if (list instanceof RandomAccess) {
                for (int i = 0; i < list.size(); i++) {
                    writer.write(list.get(i).getUrl());
                    writer.write(ls);
                }
            }
            else {
                Iterator<DownloadItem> iterator = list.iterator();
                while (iterator.hasNext()) {
                    writer.write(iterator.next().getUrl());
                    writer.write(ls);
                }
            }

            writer.flush();
            writer.close();

            LOG.info("[ LIST ] List saved to plain text file in " + (System.currentTimeMillis() - start) + " millis.");

        } catch (Exception ex) {
            new ExceptionDialog(ex);
        }
    }

}
